/*
Copyright 2007 Infordata S.p.A.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*
!!V 10/04/97 rel. 0.93 - some fixes to add SignedNumeric fields handling.
    16/04/97 rel. 0.94 - userError method added.
    08/05/97 rel. 0.95b-
    27/05/97 rel. 1.00 - first release.
    25/07/97 rel. 1.03a- a bug in ...Multicaster.
    06/08/97 rel. 1.03c- bug fix.
    27/08/97 rel. 1.04 - clipboard support.
    03/03/98 rel. _.___- SWING and reorganization.
    ***
    30/06/98 rel. _.___- Swing, JBuilder2 e VSS.
    29/07/99 rel. 1.14 - Rework on 3d look&feel.
 */
 
 
package net.infordata.em.crt5250;


import java.awt.AWTEventMulticaster;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.EventListener;

import net.infordata.em.tnprot.XITelnet;



///////////////////////////////////////////////////////////////////////////////

/**
 * Implements behaviour of 5250 fields with some extensions.
 *
 * @version
 * @author   Valentino Proietti - Infordata S.p.A.
 */
public class XI5250Field implements XI5250BaseField {

  //!!0.95b border styles
  /**
   * Uses default border definition.
   * @see    #setBorderStyle
   * @see    XI5250Crt#setDefFieldsBorderStyle
   */
  public static final int DEFAULT_BORDER = 0;
  /**
   * No border.
   * @see    #setBorderStyle
   * @see    XI5250Crt#setDefFieldsBorderStyle
   */
  public static final int NO_BORDER      = 1;
  /**
   * Raised border.
   * @see    #setBorderStyle
   * @see    XI5250Crt#setDefFieldsBorderStyle
   */
  public static final int RAISED_BORDER  = 2;
  /**
   * Lowered border.
   * @see    #setBorderStyle
   * @see    XI5250Crt#setDefFieldsBorderStyle
   */
  public static final int LOWERED_BORDER = 3;

  private XI5250Crt        ivCrt;
  private byte[]           ivFFW;
  private byte[]           ivFCW;
  private int              ivCol;
  private int              ivRow;

  //!!0.95b ivInputLen rapresent the length of the input area
  private int              ivInputLen;
  //!!0.95b this is the real field length
  private int              ivLength;

  private int              ivAttr;

  // valid only after updateStr() call
  private String       ivStr;
  private int          ivFldPos;
  private int          ivCurPos;
  private int          ivPos;

  //!!0.95
  private XI5250FieldListener      ivFieldListener;
  private XI5250FieldPaintListener ivFieldPaintListener;

  private boolean      ivEnabled = true;

  //!!0.95b
  private int          ivBorderStyle = DEFAULT_BORDER;


  /**
   * @param    aCrt    the containing XI5250Crt
   * @param    aCol    the field position
   * @param    aRow    the field position
   * @param    aLen    the field length
   * @param    aAttr   the field color attribute
   */
  public XI5250Field(XI5250Crt aCrt, int aCol, int aRow, int aLen, int aAttr) {
    this(aCrt, new byte[2], new byte[2], aCol, aRow, aLen, aAttr);
  }


  /**
   * @param    aCrt    the containing XI5250Crt
   * @param    aFFW    see IBM 5250 function reference manual page 2-68
   * @param    aFCW    see IBM 5250 function reference manual page 2-65
   * @param    aCol    the field position
   * @param    aRow    the field position
   * @param    aLen    the field length
   * @param    aAttr   the field color attribute
   */
  public XI5250Field(XI5250Crt aCrt, byte[] aFFW, byte[] aFCW,
                     int aCol, int aRow, int aLen, int aAttr) {
    ivCrt = aCrt;
    ivFFW = aFFW;
    ivFCW = aFCW;
    ivCol = aCol;
    ivRow = aRow;
    ivInputLen = aLen;
    ivLength = aLen;
    ivAttr = aAttr;

    if (isSignedNumeric())
      --ivInputLen;
  }


  /**
   */
  protected void removeNotify() {
//!!V never    ivCrt = null;
  }


  /**
   * XI5250FieldListener handling
   */
  public synchronized void addFieldListener(XI5250FieldListener l) {
    ivFieldListener = (XI5250FieldListener)Multicaster.add(ivFieldListener, l);
  }


  /**
   * XI5250FieldListener handling
   */
  public synchronized void removeFieldListener(XI5250FieldListener l) {
    ivFieldListener = (XI5250FieldListener)Multicaster.
                                               remove(ivFieldListener, l);
  }


  /**
   * Routes XI5250FieldEvent to listeners
   */
  protected void processFieldEvent(XI5250FieldEvent e) {
    if (ivFieldListener == null)
      return;

    switch (e.getID()) {
      //
      case XI5250FieldEvent.ACTIVATED:
        ivFieldListener.activated(e);
        break;
      //
      case XI5250FieldEvent.DEACTIVATED:
        ivFieldListener.deactivated(e);
        break;
      //
      case XI5250FieldEvent.VALUE_CHANGED:
        ivFieldListener.valueChanged(e);
        break;
      //
      case XI5250FieldEvent.ENABLED_STATE_CHANGED:
        ivFieldListener.enabledStateChanged(e);
        break;
      //
      case XI5250FieldEvent.KEY_EVENT:
        ivFieldListener.keyEvent(e);
        break;
    }
  }


  /**
   * XI5250FieldPaintListener handling
   */
  public synchronized void addFieldPaintListener(XI5250FieldPaintListener l) {
    ivFieldPaintListener =
        (XI5250FieldPaintListener)Multicaster.
                                      add(ivFieldPaintListener, l);
  }


  /**
   * XI5250FieldPaintListener handling
   */
  public synchronized void removeFieldPaintListener(XI5250FieldPaintListener l) {
    ivFieldPaintListener =
        (XI5250FieldPaintListener)Multicaster.
                                      remove(ivFieldPaintListener, l);
  }


  /**
   * routes XI5250FieldPaintEvent to listeners
   */
  protected void processFieldPaintEvent(XI5250FieldPaintEvent e) {
    if (ivFieldPaintListener == null)
      return;

    switch (e.getID()) {
      //
      case XI5250FieldPaintEvent.FIELD_PAINT:
        ivFieldPaintListener.fieldPaint(e);
        break;
      //
      case XI5250FieldPaintEvent.ROW_PAINT:
        ivFieldPaintListener.rowPaint(e);
        break;
    }
  }


  /**
   */
  public final XI5250Crt getCrt() {
    return ivCrt;
  }


  /**
   * Initializes the field.
   * It is called after that the 5250 panel construction has ended to let the field
   * refresh its color attribute.
   */
  public void init() {
    updateStr();

    // Is color attribute changed ?
    int newAttr = ivCrt.getAttr(ivCrt.toColPos(ivFldPos - 1), ivCrt.toRowPos(ivFldPos - 1));
    if (newAttr != ivAttr) {
      ivAttr = newAttr;
      String str = getString();
      int from = 0;
      while (true) {
        int idx = str.indexOf(XI5250Crt.ATTRIBUTE_PLACE_HOLDER, from);
        if (idx < 0) {
          ivCrt.drawString(str.substring(from), 
              ivCrt.toColPos(ivFldPos + from), ivCrt.toRowPos(ivFldPos + from), newAttr);
          break;
        }
        ivCrt.drawString(str.substring(from, idx), 
            ivCrt.toColPos(ivFldPos + from), ivCrt.toRowPos(ivFldPos + from), newAttr);
        newAttr = ivCrt.getAttr(ivCrt.toColPos(ivFldPos + idx), ivCrt.toRowPos(ivFldPos + idx));
        ivCrt.drawString(String.valueOf(XI5250Crt.ATTRIBUTE_PLACE_HOLDER), 
            ivCrt.toColPos(ivFldPos + idx), ivCrt.toRowPos(ivFldPos + idx), newAttr);
        from = idx + 1;
      }
    }
  }


  /**
   * Used for example to write field contents to the 5250 stream.
   * If needed some internal attribute can be exposed.
   */
  public void saveTo(XI5250FieldSaver aSaver) throws IOException {
    // no getTrimmedString()
    aSaver.write(this, getString());
  }


  /**
   * Clear field to nulls.
   */
  public void clear() {
    char[] chs = new char[ivLength];
    for (int i = 0; i < chs.length; i++)
      chs[i] = '\u0000';
    ivCrt.drawString(new String(chs), ivCol, ivRow, XI5250Crt.USE_PRESENT_ATTRIBUTE);
  }


  /**
   * Changes the field contents.
   */
  public void setString(String aStr) {
    if (aStr == getTrimmedString())
      return;

    /* !!1.04e
    ivCrt.drawString(aStr, ivCol, ivRow, XI5250Crt.USE_PRESENT_ATTRIBUTE);
    setMDTOn();
    */

    int  s = ivCrt.toLinearPos(ivCol, ivRow);
    char ch;
    int  col, row;
    for (int i = 0; i < aStr.length() && i < ivInputLen; i++) {
      ch = aStr.charAt(i);
      col = ivCrt.toColPos(s + i);
      row = ivCrt.toRowPos(s + i);
      insertChar(ch, col, row, false, false);
    }
  }


  /**
   * Returns value of the field as String.
   * NOTE: signed numeric are followed by sign char
   */
  public String getString() {
    String str = ivCrt.getString(ivCol, ivRow, ivLength);

    return str;
  }


  /**
   * Returns value of the field as String, all trailing nulls and blankes are cutted off.
   * NOTE: signed numeric are followed by sign char
   */
  public String getTrimmedString() {
    String str = getString();
    int    i;

    // exclude trailing null and blank chars
    for (i = str.length() - 1; (i >= 0) && ((str.charAt(i) == '\u0000') ||
                                            (str.charAt(i) == ' ')); i--)
      ;

    return (i < 0) ? "" : str.substring(0, i + 1);
  }


  /**
   * Returns the Field Format Word.
   */
  public byte getFFW(int i) {
    return ivFFW[i];
  }


  /**
   * Returns the Field Control Word.
   */
  public byte getFCW(int i) {
    return ivFCW[i];
  }


  /**
   * The field column position.
   */
  public int getCol() {
    return ivCol;
  }


  /**
   * The field row position.
   */
  public int getRow() {
    return ivRow;
  }


  /**
   * The field length.
   */
  public int getLength() {
    return ivLength;
  }


  /**
   * Called every time the field contents changes.
   * It fires the XI5250FieldEvent.VALUE_CHANGED event.
   */
  protected void setMDTOn() {
    ivFFW[0] |= 0x08;
    processFieldEvent(new XI5250FieldEvent(XI5250FieldEvent.VALUE_CHANGED, this));
  }


  /**
   */
  public void resetMDT() {
    ivFFW[0] &= ~0x08;
  }


  /**
   * True if it was created as bypass field
   */
  public boolean isOrgBypassField() {
    return ((ivFFW[0] & 0x20) != 0);
  }


  /**
   * True if it was created or it became a bypass field
   *   return (isOrgBypassField() || (!isEnabled());
   * @see    #isEnabled()
   */
  public boolean isBypassField() {
    return (isOrgBypassField() || !isEnabled());
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isDupEnabled() {
    return ((ivFFW[0] & 0x10) != 0);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isMDTOn() {
    return ((ivFFW[0] & 0x08) != 0);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isAlphabeticShift() {
    return ((ivFFW[0] & 0x07) == 0);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isAlphabeticOnly() {
    return ((ivFFW[0] & 0x07) == 0x01);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isNumericShift() {
    return ((ivFFW[0] & 0x07) == 0x02);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isNumericOnly() {
    return ((ivFFW[0] & 0x07) == 0x03);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isDigitsOnly() {
    return ((ivFFW[0] & 0x07) == 0x05);
  }


  /**
   * Used to query Field Format Word flags.<br>
   * Magnetic stripe reader, selector light pen, ...
   */
  public boolean isIOOnly() {
    return ((ivFFW[0] & 0x07) == 0x06);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isSignedNumeric() {
    return ((ivFFW[0] & 0x07) == 0x07);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isAutoEnter() {
    return ((ivFFW[1] & 0x80) != 0);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isExitRequired() {
    return ((ivFFW[1] & 0x40) != 0);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isMonocase() {
    return ((ivFFW[1] & 0x20) != 0);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isMandatoryEnter() {
    return ((ivFFW[1] & 0x08) != 0);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isRightAdjustZeroFill() {
    return ((ivFFW[1] & 0x07) == 0x05);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isRightAdjustBlankFill() {
    return ((ivFFW[1] & 0x07) == 0x06);
  }


  /**
   * Used to query Field Format Word flags.
   */
  public boolean isMandatoryFill() {
    return ((ivFFW[1] & 0x07) == 0x07);
  }


  /**
   * Key used to order fields in XI5250FieldsList.
   * Simply returns the field linear position in the 5250 buffer.
   */
  protected int getSortKey() {
    return ivCrt.toLinearPos(ivCol, ivRow);
  }


  /**
   */
  private void updateStr() {
    ivStr = ivCrt.getString(ivCol, ivRow, ivInputLen);
    //!!V se lunghezza stringa = 0 generare eccezione
    ivFldPos = ivCrt.toLinearPos(ivCol, ivRow);
    ivCurPos = ivCrt.toLinearPos(ivCrt.getCursorCol(), ivCrt.getCursorRow());
    ivPos = ivCurPos - ivFldPos;
  }


  /**
   */
  protected void processKeyEvent(KeyEvent e) {
    //!!1.04c
    processFieldEvent(new XI5250FieldEvent(XI5250FieldEvent.KEY_EVENT,
                                           this, e));

    if (e.isConsumed())
      return;

    switch (e.getID()) {
      //
      case KeyEvent.KEY_TYPED:
        if (XI5250Crt.isCharKey(e)) {
          if (processKeyChar(e.getKeyChar()))
            e.consume();
        }
        break;
      //
      case KeyEvent.KEY_PRESSED:
        if (processOtherKey(e))
          e.consume();
        break;
      //
      case KeyEvent.KEY_RELEASED:
        ivCrt.ivDropKeyChar = false;
        break;
    }
  }


  /**
   */
  protected int insertChar(char aCh, int col, int row, 
                           boolean insert, boolean fromKeyboard) {//!!1.04
    if (isIOOnly() && fromKeyboard) {
      return 4;  // Do not accept keyboard input 
    }
    
    if ((isDigitsOnly() && !(aCh >= '0' && aCh <= '9'))) {
      // 10 = only chars between 0 and 9
      return 10;
    }

    if ((isSignedNumeric() && !(aCh >= '0' && aCh <= '9')) ||
        (isNumericOnly() && !((aCh >= '0' && aCh <= '9') ||
                              aCh == ' ' || aCh == '+' || aCh == '-' ||
                              aCh == '.' || aCh == ','))
       ) {
      // 9 = numeric field
      return 9;
    }

    if (isMonocase())
      aCh = Character.toUpperCase(aCh);

    updateStr();

    ivCurPos = ivCrt.toLinearPos(col, row);
    ivPos = ivCurPos - ivFldPos;

    if (ivPos < 0 || ivPos >= ivLength)
      throw new IllegalStateException("");

    StringBuilder strBuf = new StringBuilder(ivStr);

    if (!insert) {
      strBuf.setCharAt(ivPos, aCh);

      ivCrt.drawString(new String(strBuf).substring(ivPos, ivPos + 1),
                       col, row, ivAttr);
    }
    else {
      if (strBuf.charAt(ivInputLen - 1) != '\u0000' &&
          strBuf.charAt(ivInputLen - 1) != ' ') {
        // 12 = field is full
        return 12;
      }
      else {
        strBuf.insert(ivPos, aCh);
        ivCrt.drawString(new String(strBuf).substring(ivPos, ivInputLen),
                         col, row, ivAttr);
      }
    }

    setMDTOn();   // field modified
    return 0;
  }


  /**
   */
  protected boolean processKeyChar(char aCh) {
    if (ivCrt.ivDropKeyChar) {
      ivCrt.ivDropKeyChar = false;
      return true;
    }

    int error = insertChar(aCh, ivCrt.getCursorCol(), ivCrt.getCursorRow(),
                           ivCrt.isInsertState(), true);
    if (error > 0) {
      ivCrt.userError(error);
      return true;
    }

    // is cursor on last char ??
    if (ivPos < (ivInputLen - 1))
      ivCrt.moveCursor(1, 0);
    else {
      // send auto enter (-1 as modifier) see XI5250Emulator.processKeyEnter()
      if (isAutoEnter())
        ivCrt.doProcessKeyEvent(new KeyEvent(ivCrt, KeyEvent.KEY_PRESSED, 0,
                                             -1, KeyEvent.VK_ENTER, (char)KeyEvent.VK_ENTER));
      // send auto tab
      else if (!isExitRequired())
        ivCrt.doProcessKeyEvent(new KeyEvent(ivCrt, KeyEvent.KEY_PRESSED, 0, 0,
                                             KeyEvent.VK_TAB, (char)KeyEvent.VK_TAB));
    }

    return true;
  }
  /*!!1.04
  protected boolean processKeyChar(char aCh) {
    if (ivCrt.ivDropKeyChar) {
      ivCrt.ivDropKeyChar = false;
      return true;
    }

    if ((isDigitsOnly() && !(aCh >= '0' && aCh <= '9'))) {
      // 10 = only chars between 0 and 9
      ivCrt.userError(10);
      return true;
    }

    if ((isSignedNumeric() && !(aCh >= '0' && aCh <= '9')) ||
        (isNumericOnly() && !((aCh >= '0' && aCh <= '9') ||
                              aCh == ' ' || aCh == '+' || aCh == '-' ||
                              aCh == '.' || aCh == ','))
       ) {
      // 9 = numeric field
      ivCrt.userError(9);
      return true;
    }

    if (isMonocase())
      aCh = Character.toUpperCase(aCh);

    updateStr();
    StringBuilder strBuf = new StringBuilder(ivStr);

    if (!ivCrt.isInsertState()) {
      strBuf.setCharAt(ivPos, aCh);

      ivCrt.drawString(new String(strBuf).substring(ivPos, ivPos + 1),
                       ivCrt.getCursorCol(), ivCrt.getCursorRow(), ivAttr);
    }
    else {
      if (strBuf.charAt(ivInputLen - 1) != '\u0000' &&
          strBuf.charAt(ivInputLen - 1) != ' ') {
        // 12 = field is full
        ivCrt.userError(12);
        return true;
      }
      else {
        strBuf.insert(ivPos, aCh);
        ivCrt.drawString(new String(strBuf).substring(ivPos, ivInputLen),
                         ivCrt.getCursorCol(), ivCrt.getCursorRow(), ivAttr);
      }
    }

    setMDTOn();   // field modified

    // is cursor on last char ??
    if (ivPos < (ivInputLen - 1))
      ivCrt.moveCursor(1, 0);
    else {
      // send auto enter (-1 as modifier)
      if (isAutoEnter())
        ivCrt.doProcessKeyEvent(new KeyEvent(ivCrt, KeyEvent.KEY_PRESSED, 0,
                                             -1, KeyEvent.VK_ENTER));
      // send auto tab
      else if (!isExitRequired())
        ivCrt.doProcessKeyEvent(new KeyEvent(ivCrt, KeyEvent.KEY_PRESSED, 0, 0,
                                             KeyEvent.VK_TAB));
    }

    return true;
  }
  */


  /**
   */
  protected boolean processOtherKey(KeyEvent e) {
    switch (e.getKeyCode()) {
      case KeyEvent.VK_BACK_SPACE:
        return processBackSpace(e.getModifiers());
      //
      case KeyEvent.VK_DELETE:
        return processDelete(e.getModifiers());
      //
      case KeyEvent.VK_ADD:
        ivCrt.ivDropKeyChar = true;
        doFieldExit(false);
        return true;
      //
      case KeyEvent.VK_SUBTRACT:
        ivCrt.ivDropKeyChar = true;
        if (isNumericOnly() || isSignedNumeric()) {
          doFieldExit(true);
          return true;
        }
        // 16 = field- not valid
        ivCrt.userError(16);
        return true;
      //
      case KeyEvent.VK_ENTER:
        return processEnter(e.getModifiers());
      //
      case KeyEvent.VK_END:
        return processEnd(e.getModifiers());
    }
    return false;
  }


  /**
   */
  protected boolean processBackSpace(int aModifier) {
    switch (aModifier) {
      case 0:
        updateStr();
        if (ivPos == 0) {
          ivCrt.doProcessKeyEvent(new KeyEvent(ivCrt, KeyEvent.KEY_PRESSED, 0, KeyEvent.SHIFT_MASK,
              KeyEvent.VK_TAB, (char)KeyEvent.VK_TAB));
        }
        else {
          ivCrt.moveCursor(-1, 0);
        }
        return true;
      case KeyEvent.SHIFT_MASK:
        updateStr();
        if (ivPos == 0) {
        }
        else {
          ivCrt.moveCursor(-1, 0);
          updateStr();
          StringBuilder strBuf = new StringBuilder(ivStr);
          for (int i = ivPos + 1; i < ivStr.length(); i++)
            strBuf.setCharAt(i - 1, strBuf.charAt(i));
          strBuf.setCharAt(ivStr.length() - 1, '\u0000');
          
          ivCrt.drawString(new String(strBuf).substring(ivPos),
              ivCrt.getCursorCol(), ivCrt.getCursorRow(), ivAttr);

          setMDTOn();   // field modified
        }
        return true;
    }
    return false;
  }


  /**
   */
  protected boolean processDelete(int aModifier) {
    switch (aModifier) {
      case 0:
        updateStr();
        StringBuilder strBuf = new StringBuilder(ivStr);
        for (int i = ivPos + 1; i < ivStr.length(); i++)
          strBuf.setCharAt(i - 1, strBuf.charAt(i));
        strBuf.setCharAt(ivStr.length() - 1, '\u0000');

        ivCrt.drawString(new String(strBuf).substring(ivPos),
                         ivCrt.getCursorCol(), ivCrt.getCursorRow(), ivAttr);

        setMDTOn();   // field modified
        return true;
    }
    return false;
  }


  /**
   */
  protected boolean processEnter(int aModifier) {
    switch (aModifier) {
      case KeyEvent.SHIFT_MASK:
        doFieldExit(false);
        return true;
    }
    return false;
  }


  /**
   */
  private void doFieldExit(boolean isMinus) {
    XIEbcdicTranslator translator = getCrt().getTranslator();

    updateStr();
    StringBuilder strBuf = new StringBuilder(ivStr);

    for (int i = ivPos; i < ivStr.length(); i++)
      strBuf.setCharAt(i, '\u0000');

    for ( ; ivPos > 0 && strBuf.charAt(ivPos - 1) == '\u0000'; ivPos--)
      ;

    // see IBM 5250 function reference manual page 2-70
    if (isNumericOnly()) {
      if (isMinus && ivPos > 0) {
        char ch = strBuf.charAt(ivPos - 1);
        if (ch != '+' && ch != '-' && ch != '.' && ch != ',' && ch != ' ') {
          byte xx = translator.toEBCDIC(strBuf.charAt(ivPos - 1));
          xx &= 0x0F;
          xx |= 0xD0;
          strBuf.setCharAt(ivPos - 1, translator.toChar(xx));
        }
        else {
          ivCrt.userError(26);
          return ;
        }
      }
    }

    if (isRightAdjustZeroFill() || isRightAdjustBlankFill() ||
        isSignedNumeric()) {
      int i;

      // right allign
      for (i = 0; i < ivPos; i++)
        strBuf.setCharAt(ivStr.length() - i - 1, strBuf.charAt(ivPos - i - 1));

      for (i = ivStr.length() - ivPos - 1; i >= 0; i--)
        strBuf.setCharAt(i, (isRightAdjustZeroFill()) ? '0' : ' ');

      String str = new String(strBuf);

      // see IBM 5250 function reference manual page 2-70
      if (isSignedNumeric())
        str += (isMinus) ? '-' : '\u0000';

      ivCrt.drawString(str, ivCol, ivRow, ivAttr);

    }
    else {
      ivCrt.drawString(new String(strBuf).substring(ivPos),
                       ivCrt.getCursorCol(), ivCrt.getCursorRow(), ivAttr);
    }

    setMDTOn();   // field modified

    // send tab
    ivCrt.doProcessKeyEvent(new KeyEvent(ivCrt, KeyEvent.KEY_PRESSED, 0, 0,
                                         KeyEvent.VK_TAB, (char)KeyEvent.VK_TAB));
  }


  /**
   */
  protected boolean processEnd(int aModifier) {
    switch (aModifier) {
      case 0:
        int i;
        updateStr();
        for (i = ivStr.length() - 1; (i >= 0) && (ivStr.charAt(i) <= ' '); i--)
          ;
        int xx = ivCrt.toLinearPos(ivCol, ivRow) + Math.min(i + 1, ivInputLen - 1);
        ivCrt.setCursorPos(ivCrt.toColPos(xx), ivCrt.toRowPos(xx));
        return true;
      case KeyEvent.SHIFT_MASK:
        updateStr();
        StringBuilder strBuf = new StringBuilder(ivStr);
        for (int j = ivPos; j < ivStr.length(); j++)
          strBuf.setCharAt(j, '\u0000');
          
        ivCrt.drawString(new String(strBuf).substring(ivPos),
            ivCrt.getCursorCol(), ivCrt.getCursorRow(), ivAttr);

        setMDTOn();   // field modified
        return true;
    }
    return false;
  }


  /**
   * Returns the number of rows occupied by this field
   * NOTE: a field can be wrapped on more than one line
   */
  public int getNRows() {
    return ((ivCol + ivLength - 1) / ivCrt.getCrtSize().width + 1);  //!!1.03c
  }


  /**
   * Returns the bounding rectangle (in pixel).
   */
  public Rectangle getBoundingRect() {
    Dimension charSize = ivCrt.getCharSize();
    int       rows = getNRows();

    if (rows <= 1)
      return new Rectangle(ivCol * charSize.width,
                           ivRow * charSize.height,
                           ivLength * charSize.width,
                           rows  * charSize.height);
    else
      return new Rectangle(0 * charSize.width,
                           ivRow * charSize.height,
                           //!!1.04c ivCrt.getSize().width,
                           ivCrt.getCrtBufferSize().width,
                           rows * charSize.height);
  }


  /**
   * Returns all the rows occupied by the fields.
   * NOTE: coordinates are expressed in chars.
   */
  public Rectangle[] getRows() {
    int         rows = getNRows();
    //Dimension   charSize = ivCrt.getCharSize();
    Rectangle[] rcts = new Rectangle[rows];

    // split line to handle wrap
    int len = ivLength;
    int x   = ivCol;
    int y   = ivRow;
    int dx;
    int i   = 0;

    while (len > 0) {
      dx = Math.min(ivCrt.getCrtSize().width - x, len);

      rcts[i++] = new Rectangle(x, y, dx, 1);

      len -= dx;
      x = 0;
      ++y;
    }

    return rcts;
  }


  /**
   * Returns all the rectangles that the field is composed of.
   * NOTE: coordinates are expressed in pixel and are relative to XI5250Crt panel.
   */
  public Rectangle[] getRowsRects() {
    Rectangle[] rcts = getRows();

    for (int i = 0; i < rcts.length; i++)
      rcts[i] = ivCrt.toPoints(rcts[i].x, rcts[i].y, rcts[i].width, rcts[i].height);

    return rcts;
  }


  /**
   * Repaint the field.
   */
  public void repaint() {
    Rectangle rt = getBoundingRect();
    ivCrt.repaint(rt.x, rt.y, rt.width, rt.height);
  }


  /**
   * Coordinates are relative to the field bounding rectangle
   */
  protected void paint(Graphics g) {
    processFieldPaintEvent(
        new XI5250FieldPaintEvent(XI5250FieldPaintEvent.ROW_PAINT,
                                  this, g));

    Rectangle   clip = g.getClipBounds();
    if (clip == null)   
      return;

    Rectangle[] rowsRects = getRowsRects();

    // request a paint for each row
    for (int j = 0; j < rowsRects.length; j++) {
      Rectangle fr = new Rectangle(rowsRects[j]);
      // coord relative to field bounding rect position
      fr.translate(-getBoundingRect().x, -getBoundingRect().y);

      if (clip.intersects(fr)) {
        Graphics  fg = g.create();
        // set new clipping rectangle, but do not translate origin
        fg.clipRect(fr.x, fr.y, fr.width, fr.height);
        try {
          rowPaint(fg);
        }
        finally {
          fg.dispose();
        }
      }
    }

    if (!isOrgBypassField())
      drawBorder(g);
  }


  /**
   * Draws the field borders (a field can be splitted on more than one crt
   * line).
   */
  protected void drawBorder(Graphics g) {
    int borderStyle = getUsedBorderStyle();
    if (borderStyle <= NO_BORDER)
      return;

    Rectangle[] rowsRects = getRowsRects();

    for (int i = 0; i < rowsRects.length; i++)
      rowsRects[i].translate(-getBoundingRect().x, -getBoundingRect().y);

    Color bg = getCrt().getDefBackground();
    Color cl1, cl2;
    if (isCurrentField()) {
      cl1 = (borderStyle == RAISED_BORDER) ? bg.darker().darker().darker() :
                                             bg.darker();
      cl2 = (borderStyle == RAISED_BORDER) ? bg.darker() :
                                             bg.darker().darker().darker();
    }
    else {
      cl1 = (borderStyle == RAISED_BORDER) ? bg.darker() :
                                             bg;
      cl2 = (borderStyle == RAISED_BORDER) ? bg :
                                             bg.darker();
    }

    switch (rowsRects.length) {
      //
      case 0:
        break;
      //
      case 1:
        g.setColor(cl1);
        g.drawLine(rowsRects[0].x,
                   rowsRects[0].y,
                   rowsRects[0].x,
                   rowsRects[0].y + rowsRects[0].height - 1);
        g.drawLine(rowsRects[0].x,
                   rowsRects[0].y,
                   rowsRects[0].x + rowsRects[0].width - 1,
                   rowsRects[0].y);

        g.setColor(cl2);
        g.drawLine(rowsRects[0].x + rowsRects[0].width - 1,
                   rowsRects[0].y,
                   rowsRects[0].x + rowsRects[0].width - 1,
                   rowsRects[0].y + rowsRects[0].height - 1);
        g.drawLine(rowsRects[0].x,
                   rowsRects[0].y + rowsRects[0].height - 1,
                   rowsRects[0].x + rowsRects[0].width - 1,
                   rowsRects[0].y + rowsRects[0].height - 1);
        break;
      //
      case 2:
        int dx = Math.max(0, (rowsRects[1].x + rowsRects[1].width - 1) - rowsRects[0].x);

        g.setColor(cl1);
        g.drawLine(rowsRects[0].x,
                   rowsRects[0].y,
                   rowsRects[0].x,
                   rowsRects[0].y + rowsRects[0].height - 1);
        g.drawLine(rowsRects[0].x,
                   rowsRects[0].y,
                   rowsRects[0].x + rowsRects[0].width - 1,
                   rowsRects[0].y);

        g.drawLine(rowsRects[1].x,
                   rowsRects[1].y,
                   rowsRects[1].x + rowsRects[1].width - 1 - dx,
                   rowsRects[1].y);

        g.setColor(cl2);
        g.drawLine(rowsRects[1].x + rowsRects[1].width - 1,
                   rowsRects[1].y,
                   rowsRects[1].x + rowsRects[1].width - 1,
                   rowsRects[1].y + rowsRects[1].height - 1);
        g.drawLine(rowsRects[1].x,
                   rowsRects[1].y + rowsRects[1].height - 1,
                   rowsRects[1].x + rowsRects[1].width - 1,
                   rowsRects[1].y + rowsRects[1].height - 1);

        g.drawLine(rowsRects[0].x + dx,
                   rowsRects[0].y + rowsRects[0].height - 1,
                   rowsRects[0].x + rowsRects[0].width - 1,
                   rowsRects[0].y + rowsRects[0].height - 1);
        break;
      //
      default:
        int l = rowsRects.length - 1;

        g.setColor(cl1);
        g.drawLine(rowsRects[0].x,
                   rowsRects[0].y,
                   rowsRects[0].x,
                   rowsRects[0].y + rowsRects[0].height - 1);
        g.drawLine(rowsRects[0].x,
                   rowsRects[0].y,
                   rowsRects[0].x + rowsRects[0].width - 1,
                   rowsRects[0].y);

        g.drawLine(rowsRects[1].x,
                   rowsRects[0].y + rowsRects[0].height - 1,
                   rowsRects[0].x,
                   rowsRects[0].y + rowsRects[0].height - 1);

        g.setColor(cl2);
        g.drawLine(rowsRects[l].x + rowsRects[l].width - 1,
                   rowsRects[l].y,
                   rowsRects[l].x + rowsRects[l].width - 1,
                   rowsRects[l].y + rowsRects[l].height - 1);
        g.drawLine(rowsRects[l].x,
                   rowsRects[l].y + rowsRects[l].height - 1,
                   rowsRects[l].x + rowsRects[l].width - 1,
                   rowsRects[l].y + rowsRects[l].height - 1);

        g.drawLine(rowsRects[l].x + rowsRects[l].width - 1,
                   rowsRects[l - 1].y + rowsRects[l - 1].height - 1,
                   rowsRects[l - 1].x + rowsRects[l - 1].width - 1,
                   rowsRects[l - 1].y + rowsRects[l - 1].height - 1);
        break;
    }
  }


  /**
   * Called for each rectangle returned by getRowsRects.
   * Coordinates are relative to the field bounding rectangle.
   * @see    #getRowsRects
   */
  protected void rowPaint(Graphics g) {
    processFieldPaintEvent(new XI5250FieldPaintEvent(XI5250FieldPaintEvent.ROW_PAINT,
                                                     this, g));
    if (!ivEnabled) {
      Rectangle rt = getBoundingRect();
      g.setColor(Color.white);

      int hh = rt.height;
      for (int x = -hh; x < rt.width; x += 8)
        g.drawLine(x, hh, x + hh, 0);
    }
  }


  /**
   * Field has changed its dimensions.
   */
  protected void resized() {
  }


  /**
   * Field has changed its activated (focus) state.
   */
  protected void activated(boolean activated) {
    repaint();    //!!1.15
    processFieldEvent(new XI5250FieldEvent((activated) ? XI5250FieldEvent.ACTIVATED :
                                                         XI5250FieldEvent.DEACTIVATED,
                                           this));

  }


  /**
   */
  public final boolean isCurrentField() {
    return ivCrt.getCurrentField() == this;
  }


  /**
   * Enable or disable the field.
   */
  public void setEnabled(boolean flag) {
    if (flag == ivEnabled)
      return;
    ivEnabled = flag;
    repaint();
    processFieldEvent(
        new XI5250FieldEvent(XI5250FieldEvent.ENABLED_STATE_CHANGED, this));
  }


  /**
   */
  public boolean isEnabled() {
    return ivEnabled;
  }


  /**
   * Changes the field border style.
   */
  public void setBorderStyle(int aStyle) {
    if (aStyle == ivBorderStyle)
      return;

    if (aStyle < DEFAULT_BORDER || aStyle > LOWERED_BORDER)
      throw new IllegalArgumentException("Wrong border style argument");

    ivBorderStyle = aStyle;
    repaint();
  }


  /**
   * @see    #getUsedBorderStyle
   */
  public int getBorderStyle() {
    return ivBorderStyle;
  }


  /**
   * Returns the real used border style.
   * It takes care of the default border style defined with
   * XI5250Crt.setDefFieldsBorderStyle
   * @see    XI5250Crt#setDefFieldsBorderStyle
   */
  public int getUsedBorderStyle() {
    int style = getBorderStyle();
    return (style != DEFAULT_BORDER) ? style : ivCrt.getDefFieldsBorderStyle();
  }


  /**
   */
  @Override
  public String toString() {
    return super.toString() + " [FFW=[" + XITelnet.toHex(ivFFW[0]) + "," +
                                          XITelnet.toHex(ivFFW[1]) + "]," +
                              "FCW=[" + XITelnet.toHex(ivFCW[0]) + "," +
                                        XITelnet.toHex(ivFCW[1]) + "]," +
                              "Attr=" + XITelnet.toHex((byte)ivAttr) + "," +
                              "(" + ivCol + "," + ivRow + ")" +
                              "Len=" + ivLength + "]";
  }


  //////////////////////////////////////////////////////////////////////////////

  /**
   * Multicast Listener for XI5250Field
   * @version
   * @author   Valentino Proietti - Infordata S.p.A.
   */
  private static class Multicaster extends AWTEventMulticaster
                                   implements XI5250FieldListener,
                                              XI5250FieldPaintListener {


    protected Multicaster(EventListener a, EventListener b) {
      super(a, b);
    }


    //!!1.03a
    @Override
    protected EventListener remove(EventListener oldl) {
      if (oldl == a)  return b;
      if (oldl == b)  return a;
      EventListener a2 = removeInternal(a, oldl);
      EventListener b2 = removeInternal(b, oldl);
      if (a2 == a && b2 == b)
        return this;
      return add(a2, b2);
    }


    public static EventListener add(EventListener a, EventListener b) {
      if (a == null)  return b;
      if (b == null)  return a;
      return new Multicaster(a, b);
    }


    public static EventListener remove(EventListener a, EventListener b) {
      return removeInternal(a, b);
    }


    /**
     */
    public void activated(XI5250FieldEvent e) {
      ((XI5250FieldListener)a).activated(e);
      ((XI5250FieldListener)b).activated(e);
    }


    public void deactivated(XI5250FieldEvent e) {
      ((XI5250FieldListener)a).deactivated(e);
      ((XI5250FieldListener)b).deactivated(e);
    }


    public void valueChanged(XI5250FieldEvent e) {
      ((XI5250FieldListener)a).valueChanged(e);
      ((XI5250FieldListener)b).valueChanged(e);
    }


    public void enabledStateChanged(XI5250FieldEvent e) {
      ((XI5250FieldListener)a).enabledStateChanged(e);
      ((XI5250FieldListener)b).enabledStateChanged(e);
    }


    public void keyEvent(XI5250FieldEvent e) {
      ((XI5250FieldListener)a).keyEvent(e);
      ((XI5250FieldListener)b).keyEvent(e);
    }


    /**
     */
    public void fieldPaint(XI5250FieldPaintEvent e) {
      ((XI5250FieldPaintListener)a).fieldPaint(e);
      ((XI5250FieldPaintListener)b).fieldPaint(e);
    }


    public void rowPaint(XI5250FieldPaintEvent e) {
      ((XI5250FieldPaintListener)a).rowPaint(e);
      ((XI5250FieldPaintListener)b).rowPaint(e);
    }
  }
}
